﻿// Original author contact info: Owen Emlen (owene_1998@yahoo.com)
// Note: other individuals may also have contributed to this code
// Project hosted on CodePlex.com as of 1/10/2009 at http://wpfimageviewer.codeplex.com
using System;
using System.Threading;
using System.Windows;
using BrainTechLLC;
using BrainTechLLC.ThreadSafeObjects;
using System.Diagnostics;

namespace ImageAppDataModels
{
    public class ActionAndCallback<T> where T : class
    {
        public Action<T> _callback;
        public T _item;
        public object _lock;
        public bool _cancelled;
        public Action<ActionAndCallback<T>, T> _performAction;

        public ActionAndCallback(T item, Action<ActionAndCallback<T>, T> performAction, Action<T> callback)
        {
            _callback = callback;
            _item = item;
            _performAction = performAction;
        }
    }

    public class LazyLoader<T> where T : class
    {
        public ThreadSafeQueue<ActionAndCallback<T>> _loadQueue;
        public ThreadSafeList<ActionAndCallback<T>> _working = new ThreadSafeList<ActionAndCallback<T>>();
        public object _lockObj = new object();
        public Action<ActionAndCallback<T>, T> _loadItemAction;
        public bool _initialized;
        public bool _isBusy;
        public T _currentItem;
        public bool _manualRemovalFromWorkingList;
        public ThreadPriority _priority;

        public LazyLoader(Action<ActionAndCallback<T>, T> loadItemAction, bool manualRemovalFromWorkingList, ThreadPriority priority)
        {
            _priority = priority;
            _manualRemovalFromWorkingList = manualRemovalFromWorkingList;
            _loadItemAction = loadItemAction;
        }

        public bool AlreadyExists(T item)
        {
            if (_loadQueue != null)
            {
                if (_loadQueue.IsInList((v) => v._item.Equals(item)))
                {
                    return true;
                }
            }

            if (_working.Find((v) => v._item.Equals(item)) != null)
                return true;

            return false;
        }

        public bool LazyLoad(T item, Action<T> callback)
        {
            return LazyLoad(item, callback, null);
        }

        public bool IsBusy
        {
            get { return _isBusy; }
        }

        public T CurrentItem
        {
            get { return _currentItem; }
        }

        public void SetTopPriority(T item)
        {
            if (_loadQueue != null)
            {
                var allItems = _loadQueue.AllItems;

                var found = allItems.Find((v) => v._item.Equals(item));

                if (found != null)
                {
                    CancelAllBut(found._item);
                    allItems.Remove(found);

                    for (int n = 0; n < allItems.Count; n++)
                    {
                        var requeueItem = allItems[n];
                        LazyLoad(requeueItem._item, requeueItem._callback, requeueItem._lock);
                    }
                }
            }
        }

        public void CancelAll()
        {
            if (_loadQueue != null)
            {
                var allItems = _loadQueue.AllItems;

                _loadQueue.Clear();

                for (int n = 0; n < allItems.Count; n++)
                {
                    var item = allItems[n];

                    if (item._callback != null)
                    {
                        item._callback(item._item);
                    }
                }
            }
        }

        public int Count
        {
            get { return _loadQueue == null ? 0 : _loadQueue.Count; }
        }

        public void CancelAllBut(T itemNotToCancel)
        {
            if (_loadQueue != null)
            {
                var allItems = _loadQueue.AllItems;
                var found = allItems.Find((v) => v._item.Equals(itemNotToCancel));

                _loadQueue.Clear();

                if (found != null)
                {
                    _loadQueue.Enqueue(found);
                }

                for (int n = 0; n < allItems.Count; n++)
                {
                    var item = allItems[n];

                    if (item == found)
                    {
                        continue;
                    }

                    if (item._callback != null)
                    {
                        item._callback(item._item);
                    }
                }
            }
        }

        public bool IsEnqueued(T item)
        {
            return AlreadyExists(item);
        }

        public bool LazyLoad(T item, Action<T> callback, object lockObj)
        {
            if (AlreadyExists(item))
                return false;

            CheckInit();

            var actionAndCallback = new ActionAndCallback<T>(item, _loadItemAction, callback);

            if (lockObj != null)
            {
                actionAndCallback._lock = lockObj;
            }

            _loadQueue.Enqueue(actionAndCallback);

            return true;
        }

        public void CheckInit()
        {
            _lockObj.CheckInitialization(ref _initialized, () =>
            {
                _loadQueue = new ThreadSafeQueue<ActionAndCallback<T>>();
                ThreadStart ts = new ThreadStart(LazyLoadRunner);
                Thread t = ts.CreateAndRunThread(true);
                t.Name = "LazyLoadRunner" + typeof(T).Name;
                t.Priority = _priority;
            });
        }


        public void LazyLoadRunner()
        {
            while (true)
            {
                try
                {
                    ActionAndCallback<T> item;
                    bool any = false;

                    if (_loadQueue.Dequeue(out item))
                    {
                        //Console.WriteLine("Lazy load runner: " + item._item.ToString());
                        _working.Add(item);

                        if (_working.Count > 100)
                        {
                            Console.WriteLine(_working.Count.ToString());
                        }

                        any = true;

                        try
                        {
                            if (!item._cancelled)
                            {
                                if (item._lock != null)
                                {
                                    lock (item._lock)
                                    {
                                        _isBusy = true;
                                        _currentItem = item._item;

                                        try
                                        {
                                            if (Application.Current == null)
                                                break;

                                            item._performAction(item, item._item);
                                        }
                                        catch
                                        {
                                            if (_manualRemovalFromWorkingList)
                                            {
                                                _working.Remove(item);
                                            }
                                        }
                                        finally
                                        {
                                            _isBusy = false;
                                            _currentItem = null;
                                        }
                                    }
                                }
                                else
                                {
                                    _isBusy = true;
                                    _currentItem = item._item;

                                    try
                                    {
                                        if (Application.Current == null)
                                            break;

                                        item._performAction(item, item._item);
                                    }
                                    catch
                                    {
                                        if (_manualRemovalFromWorkingList)
                                        {
                                            _working.Remove(item);
                                        }
                                    }
                                    finally
                                    {
                                        _isBusy = false;
                                        _currentItem = null;
                                    }
                                }

                                if (item._callback != null)
                                {
                                    item._callback(item._item);
                                }
                            }
                        }
                        catch
                        {
                            if (_manualRemovalFromWorkingList)
                            {
                                _working.Remove(item);
                            }
                        }
                        finally
                        {
                            if (!_manualRemovalFromWorkingList)
                            {
                                _working.Remove(item);
                            }
                        }
                    }

                    if (!any)
                        Thread.Sleep(42);
                    else
                        Thread.Sleep(3); // was 5
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.ToString());
                    Debug.WriteLine(ex.ToString());
                    Debug.Assert(false);
                }
            }
        }
    }
}
